package biolabs.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import scala.collection.JavaConversions;
import biolabs.core.BioContainer;
import biolabs.core.BioObject;
import biolabs.core.Experiment;

public class CompiledQuery implements Query {

	private final Axis[] axes;
	private final Predicate[] predicates;

	CompiledQuery(List<Axis> axes, List<Predicate> predicates) {
		assert  predicates.size() == axes.size();
		assert predicates.size() > 0;
		if(predicates.size() > 64)
			throw new UnsupportedOperationException("Queries with more than 64 levels are not supported.");
		this.axes = axes.toArray(new Axis[axes.size()]);
		this.predicates = predicates.toArray(new Predicate[predicates.size()]);
	}

	@Override
	public CompiledQuery compile() {
		return this;
	}

	@Override
	public Collection<BioObject> execute(final Experiment exp) throws QueryExecutionException {
		try {
			return exp.executeUnderReadLock(new Callable<Collection<BioObject>>() {
				@Override
				public Collection<BioObject> call() throws Exception {
					return _execute(exp, exp.getRoot());
				}
			});
		} catch(ExecutionException e) {
			throw new QueryExecutionException(e.getCause());
		}
	}

	@Override
	public Collection<BioObject> execute(final Experiment exp, final BioObject queryRoot) throws QueryExecutionException {
		try {
			return exp.executeUnderReadLock(new Callable<Collection<BioObject>>() {
				@Override
				public Collection<BioObject> call() throws Exception {
					return _execute(exp, queryRoot);
				}
			});
		} catch(ExecutionException e) {
			throw new QueryExecutionException(e.getCause());
		}
	}

	private Collection<BioObject> _execute(Experiment exp, BioObject queryRoot) {
		long state = 1;
		Collection<BioObject> res;
		if(axes[0].selectsSelf()) {
			res = processNode(queryRoot, state);
		} else if(queryRoot instanceof BioContainer<?>) {
			res = new ArrayList<BioObject>();
			for(BioObject o: JavaConversions.asJavaIterable(((BioContainer<?>)queryRoot).getChildren())) // TODO: concurrently
				res.addAll(processNode(o, state));
		} else {
			res = Collections.emptyList();
		}
		return res;
	}

	private Collection<BioObject> processNode(BioObject node, long state) {

		Collection<BioObject> res = new ArrayList<BioObject>();

		long newState = 0;
		long s = state;
		while(s > 0) {
			for(int i=0; i<axes.length; ++i) {
				if((1<<i & s) > 0) {
					s ^= 1<<i;
					if(predicates[i].eval(node)) {
						if(i == axes.length-1) {
							res.add(node);
						} else {
							if(axes[i+1].selectsSelf())
								s |= 1<<(i+1);
							if(axes[i+1] != Axis.SELF)
								newState |= 1<<(i+1);
						}
					}
				}
			}
		}

		for(int i=0; i<axes.length; ++i) {
			if((1<<i & state) > 0) {
				if(axes[i].selectsDescendants())
					newState |= 1<<i;
			}
		}

		if(node instanceof BioContainer<?>) {
			for(BioObject o: JavaConversions.asJavaIterable(((BioContainer<?>)node).getChildren()))
				res.addAll(processNode(o, newState)); // TODO: concurrently
		}

		return res;
	}
}
